package hydrap2p.proxy.manager;

import hydrap2p.ServiceLocation;
import hydrap2p.logging.Level;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class ServiceTunnels {
	private static ServiceTunnels instance;
	private Map<Integer,ServiceTunnel> tunnels;		// map local port to tunnel info
	private DoubleMap<Integer, ServiceLocation, ServiceTunnel> remotetunnels;	// map remote port to tunnel info 
	
	private ServiceTunnels()
	{
		tunnels=new TreeMap<Integer,ServiceTunnel>();
		remotetunnels=new DoubleMap<Integer, ServiceLocation, ServiceTunnel>();
	}
	public static ServiceTunnels getInstance()
	{
		if (instance==null)
		{
			synchronized(ServiceTunnels.class)
			{
				if (instance==null)
					instance=new ServiceTunnels();
			}
		}
		return instance;
	}
	
	/**
	 * Creates a tunnel connection between a random local port and the remote port
	 * @param peerid
	 * @param remoteport
	 * @return
	 * @throws IOException
	 */
	public ServiceLocation createConnection(int peerid, ServiceLocation remote) throws IOException
	{
		//ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Creating new tunnel to peerid "+peerid+" and remote port "+remote);

		ServiceTunnel temp=new ServiceTunnel(peerid, remote);
		synchronized(tunnels)
		{
			tunnels.put(temp.getPort(),temp);
			remotetunnels.put(peerid, remote, temp);
		}
		new Thread(temp,temp.toString()).start();
		ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Created new tunnel to peerid "+peerid+" and remote port "+remote+" at port "+temp.getPort());
		return new ServiceLocation("localhost",temp.getPort());
	}
	
	public void removeTunnel(int port)
	{
		ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Removing tunnel at port "+port);
		synchronized(tunnels)
		{
			if (tunnels.containsKey(port))
			{
				tunnels.get(port).shutdown();
				remotetunnels.remove(tunnels.get(port).getPeerID(), tunnels.get(port).getRemotePort());
			}
			tunnels.remove(port);
			
		}
	}
	

	/**
	 * Remove any connections to this peerid
	 * @param peerid
	 */
	public void removePeerTunnels(int peerid)
	{
		ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Removing all tunnels owned by "+peerid);
		synchronized(tunnels)
		{
			List<Integer> toRemove=new LinkedList<Integer>();
			for (Map.Entry<Integer,ServiceTunnel> tunnel:tunnels.entrySet())
			{
				if (tunnel.getValue().getPeerID()==peerid)
					toRemove.add(tunnel.getKey());
			}
			for (int port:toRemove)
			{
				if (tunnels.containsKey(port))
				{
					tunnels.get(port).shutdown();
					remotetunnels.remove(tunnels.get(port).getPeerID(), tunnels.get(port).getRemotePort());
				}
			}
		}
	}
	
	/**
	 * Given an existing tunnel port, find the peerid for it
	 * @param port
	 * @return
	 */
	public int findPeerID(int port)
	{
		//ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Finding the remote peerid for port "+port);

		ServiceTunnel tunnel=tunnels.get(port);
		if (tunnel==null)
			throw new IllegalArgumentException("Local port not registered: "+port);
		return tunnel.getPeerID();
	}
	
	public void returnData(int peerid, ServiceLocation server, ServiceLocation client, byte[] data)
	{
		//ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Returning data from "+server+" to "+client);

		ServiceTunnel connection=remotetunnels.get(peerid, server);
		if (connection!=null)
		{
			connection.returnData(client, data);
		}
	}
	
	public void closeDataConnection(int peerid, ServiceLocation server, ServiceLocation client)
	{
		//ProxyLogging.log(Level.DEBUG, "ProxyManager - ServiceTunnels", "Closing data connection to "+server+" from "+client);

		ServiceTunnel connection=remotetunnels.get(peerid, server);
		if (connection!=null)
		{
			connection.closeDataConnection(client.port);
		}
	}
}
